/** This class loads all available plugins. 
 *  @author Copyright (c) 2004-2005 by chrmac@gmx.de */

package info.chrm.jtalker;

import java.net.*;
import java.io.*;
import java.lang.reflect.Method;
import java.util.ArrayList;

/** This class provides an access interface for all plugins.
 *  @author ChrM */
public class JTalkerClientPluginLoader {
	private JTalkerClientFront parent;
	private JTalkerClientPlugInInterface allPlugins [];
    	public static final String PLUGIN_NAMESPACE = "jtplugin";
        private String lastErrorMsg = null;

	/** C'tor */
	public JTalkerClientPluginLoader(JTalkerClientFront _parent) {
		this.parent = _parent;
	}

        /** Initializes all plugins */
        public boolean initPlugins(String _searchURL) {
                // Deregister from command responses first
                if(allPlugins != null) {
                    for( int i = 0; i < allPlugins.length; i++ ) {
                        System.out.println("[PLUGIN-LOADER]: Unloading: " + allPlugins[i].getName());
                        allPlugins[i].unload();                    
                    }
                }

		if(!this.loadPlugIns(_searchURL)) {
                        System.err.println("*** " + this.lastErrorMsg);
			System.err.println("*** Can't init plugins. Disable.");
                        return false;
		}
                System.out.println("[PLUGIN-LOADER]: Number of loaded plugins:" + allPlugins.length);

                return true;
        }

        /** If an error occurs, this method gets an error string. Otherwise it returns null. */
        public String getLastError() {
            String tmpRetVal = this.lastErrorMsg;
            this.lastErrorMsg = null;
            return tmpRetVal;
        }
        

        //////////////////////////// Loading Plugins ////////////////////////////////////
        
	/** Gets a list of loaded plugin class names */
	public String[] getPluginClassNames() {
                if(allPlugins == null || allPlugins.length == 0)
                    return null;

                String [] retVal = new String[allPlugins.length];
                
                for( int i = 0; i < allPlugins.length; i++ ) {
                    retVal[i] = allPlugins[i].getClass().getName();                    
                }
		
                return retVal;
	}

        /** Gets a list of loaded plugin names */
	public String[] getPluginNames() {
                if(allPlugins == null || allPlugins.length == 0)
                    return null;

                String [] retVal = new String[allPlugins.length];
                for( int i = 0; i < allPlugins.length; i++ ) {
                    retVal[i] = allPlugins[i].getName();                    
                }
		
                return retVal;
	}

        /** Shows the main GUI for a given Plugin 
         *  @param _pluginName Plugins Name 
         *  @param _nameType type of the name. 0: Class 1: real name */
        public boolean showPluginsGUI(String _pluginName, int _nameType) {
            try {
                if(_nameType == 0)
                    this.getPluginByClassName(_pluginName).getMainFrame().setVisible(true);
                else
                    this.getPluginByName(_pluginName).getMainFrame().setVisible(true);
            } catch (Exception _e) {
                return false;
            } 

            return true;
        }

        /** Shows the settings GUI for a given Plugin 
         *  @param _pluginName Plugins Name 
         *  @param _nameType type of the name. 0: Class 1: real name */
        public boolean showPluginsSettings(String _pluginName, int _nameType) {
            try {
                if(_nameType == 0)
                    this.getPluginByClassName(_pluginName).getSettingsFrame().setVisible(true);
                else
                    this.getPluginByName(_pluginName).getSettingsFrame().setVisible(true);
            } catch (Exception _e) {
                return false;
            } 
            
            return true;
        }

        /** Gets Plugin-Object by class name */
        public JTalkerClientPlugInInterface getPluginByClassName(String _classname) {
            JTalkerClientPlugInInterface retVal = null;
            
            for( int i = 0; i < allPlugins.length; i++ ) {
                    retVal = allPlugins[i];
                    if(retVal.getClass().getName().equals(_classname))
                        return retVal;
            }

            return null;
        }
        
        /** Gets the first Plugin-Object matches the name */
        public JTalkerClientPlugInInterface getPluginByName(String _name) {
            JTalkerClientPlugInInterface retVal = null;
            
            for( int i = 0; i < allPlugins.length; i++ ) {
                    retVal = allPlugins[i];
                    if(retVal.getName().equals(_name))
                        return retVal;
            }
            
            return null;
        }

	/** Tryes to load all plugins in the given directory 
     	*  @param _fromDir Search for plugins in this directory 
     	*  @return False, if something went wrong.
     	*  @see getLastError() */
    
    	private boolean loadPlugIns(String _fromDir) {
                // Check, if its a dir
                if(!new File(_fromDir).isDirectory()) {
                    this.lastErrorMsg = "Not a directory: " + _fromDir;
                    return false;
                }
                
		// Search for jar-files and init plugins
		String searchURL = _fromDir;
		
		// Parse directory content for "*.jar"
		String [] allPluginFiles = getPlugInNames(searchURL);
		
		// Found nothing
		if(allPluginFiles == null) {
		    this.lastErrorMsg = "No plugins found in " + searchURL;
		    return false;
		}
		
		// Create array
		JTalkerClientPlugInInterface [] tmpAllPlugins = new JTalkerClientPlugInInterface [allPluginFiles.length];
                int cnt = 0; // Real number of succesfull loaded plugins
		
		try {
		    // Preload all PlugIns
		    URL[] urlsToLoadFrom = new URL[allPluginFiles.length];
		    
		    for(int i=0; i<allPluginFiles.length; i++) {
                        URI tmpURI = new File(searchURL + allPluginFiles[i]).toURI();
			urlsToLoadFrom[i] = tmpURI.toURL();
			System.out.println("+ Searching in: " + urlsToLoadFrom[i].toString());
		    }
            
		    //URL[] urlsToLoadFrom = new URL[]{new File(searchURL).toURL()};
		    URLClassLoader loader = new URLClassLoader(urlsToLoadFrom);
		    
		    Class tmpClass;
		    
		    for( int i = 0; i < allPluginFiles.length; i++ ) {
			try {		    
				tmpClass = 
					Class.forName(PLUGIN_NAMESPACE + "." + allPluginFiles[i].replaceAll("\\.jar", ""), true, loader);
	
                                JTalkerClientPlugInInterface tmpObj = (JTalkerClientPlugInInterface)tmpClass.newInstance();
                                
                                if(tmpObj != null) {
				    tmpAllPlugins[i] = tmpObj;
				    tmpAllPlugins[i].setParentObject(new JTalkerClientPluginConnector(this.parent));
                                    System.out.println("+ Init Plugin: " + tmpAllPlugins[i].getName() + " --> OK.");
                                    cnt++;
                                } else 				
                                    System.err.println("*** Can't init plugin: " + allPluginFiles[i] + ". Disable this plugin.");
                                
			} catch (Exception _e1) {
				System.err.println("*** Can't init plugin: " + allPluginFiles[i] + ". Disable this plugin.");
				_e1.printStackTrace();
			} 
		    }
		} catch (Exception _e) {
		    _e.printStackTrace();
            
		    // Disable plugins
		    this.allPlugins = null;
		    this.lastErrorMsg = "Exception: " + _e.toString();
		    return false;
		}
        
                // Copy all loaded plugins to the allPluginsArray
                this.allPlugins = new JTalkerClientPlugInInterface [cnt];
                
                int j = 0;
                
                for( int i = 0; i < tmpAllPlugins.length; i++ ) { 
                    if(tmpAllPlugins[i] != null) {
                        this.allPlugins[j] = tmpAllPlugins[i];
                        j++;
                    }
                }
                
		return true;
	}

	/** Gets an array of PlugIn names in given URL or null.
     	*  @param _searchURL PlugIn directory. If null, use "./plugins" */
    	private static String[] getPlugInNames(String _searchURL) {
		if(_searchURL == null)
		    _searchURL = "./plugins";
		
		File dir = new File(_searchURL);
		
		FilenameFilter filter = new FilenameFilter() {
		    public boolean accept(File dir, String name) {
			return name.endsWith(".jar");
		    }
		};
		
		String plugins [] = dir.list(filter);
		
		if(plugins == null) {
		    return null;
		}
		
		return plugins;
	}


        ///////////////// Invoking methods ////////////////////////


        /** Parses and invokes a method if plugIn is available, otherwise it returns null 
        *  @param _method Method for a PlugIn. The String looks like this: plugin::method("parameter")
        *  @param _caller User who call this method
        *  @return The result of the method or null if somthing went wrong - in this case read the last error. 
        *  @see getLastError() */
   
        public synchronized String invokePlugInMethod(String _method, String _caller) {
          // Reset ErrorMsg
          this.lastErrorMsg = null;
          _method = _method.trim();
          
          System.out.println("+ Invoking: " + _method + ", recieved from: " + _caller);

          try { 
             
             if(_method == null || _method.indexOf("::") == -1) {
                this.lastErrorMsg = "Syntax error. Try module::method!";
                return null;
             }
	 
             String [] tmpValues = _method.split("::");
             
             if(tmpValues.length != 2) {
                this.lastErrorMsg = "Syntax error. Try module::method!";
                return null;
             }
	 
             String plugin = tmpValues[0];
             String method = tmpValues[1];
             JTalkerClientPlugInInterface tmpPlugInObj = null;
             
             if(plugin == null || plugin.equals("") || method == null || method.equals("")) {
                this.lastErrorMsg = "Syntax error";
                return null;
             }

             // Std-Server-Method?
             if(plugin.equalsIgnoreCase("std")) {
                return invokeBuildInMethod(method, _caller);
             }
      
             if(this.allPlugins == null) {
                this.lastErrorMsg = "No plugin loaded.";
                return null;
             }
	 
             // Check if plugin is available
             boolean hit = false;
             
             for( int i = 0; i < this.allPlugins.length; i++ ) {
                 if(plugin.equals(this.allPlugins[i].getClass().getName())) {
                     hit = true;
                     tmpPlugInObj = this.allPlugins[i];
                     break;
                 }
             }
	 
             if(!hit) {
                 this.lastErrorMsg = "Plugin is not available on this client: " + plugin;
                 return null;
             }
      
             // TODO: Call plugins method here!

             try {
                Method [] plugInsMethods = tmpPlugInObj.getCommandMethods();
                method = method.trim();

                // Say PlugIn who's calling
                //tmpPlugInObj.setCaller(_caller.getUserName(), _caller.getUserIP());
               
                // Gets all available commands for remote using
                if(method.equals("getMethods()")) {                   
                   String methodNamesInLine = "";
                   
                   for( int i = 0; i < tmpPlugInObj.getCommandMethods().length; i++ ) { 
                      methodNamesInLine += tmpPlugInObj.getCommandMethods()[i].toString();
                      methodNamesInLine += ";;";
                   }
                   
                   return methodNamesInLine;
                   
                } else {
                   // Parse and ivoke special plugin method
                   System.out.println("+ Using Plugin: " + tmpPlugInObj.getName() + " caller: " + _caller);
                   String tmpMethodName = (method.substring(0, method.indexOf('('))).trim();

                   String [] params = JTalkerClientPluginLoader.parseParams(method);
                   
                   for( int i = 0; i < plugInsMethods.length; i++ ) {
                      if(plugInsMethods[i].getName().equals(tmpMethodName)) {
                         // HIT!		     
                         String retVal = (String)plugInsMethods[i].invoke(tmpPlugInObj, params);

                         // Reset callers data
                         //tmpPlugInObj.setCaller(null, null);
		     
                         return retVal;
                      }
                   }

                   this.lastErrorMsg = "Method not declared: " + method;
                   return null;
                }	    
	    
             } catch (Exception _e) {
                 System.out.println(
                         "*** ERROR: Can't invoke PlugIn method (" + plugin + "::" + method + "): " + _e.toString());
                 this.lastErrorMsg = "Can't invoke method: " + method + ". " + _e.toString();
                 return null;
             } 

          } catch (Exception _e) {
              _e.printStackTrace();	 
              this.lastErrorMsg = "Can't invoke method: " + _method + ". " + _e.toString();
          }

      
          this.lastErrorMsg = "Unexpected Error. Can't invoke this method";      
          return null;         
        }        

        /** Invoke Buildin Method 
        *  @param _method Method to invoke
        *  @param _caller User who call this method
        *  @return Return value of the invoked method
        * */       
        private String invokeBuildInMethod(String _method, String _caller) {
            // remove spaces
            _method = _method.replaceAll(" ", "");
      
            if(_method.equals("getClientInfo()")) {
                //return this.parent.getClientInfo();
            }

            return "Unknown method: " + _method;
        }
        


        /** Parsing method parameter */
        private static String [] parseParams(String _in) {
            
            String tmpParameters = _in.substring(_in.indexOf('(') + 1, _in.lastIndexOf(')'));
            String [] params = tmpParameters.split("\"[\\s]*,[\\s]*\"");
          
            if(params == null || params.length < 1 || (params.length == 1 && params[0].equals(""))) {
                return null;
            }
          
            else {
                params[0] = params[0].replaceFirst("\"", "");
                params[params.length-1] = params[params.length-1].replaceFirst("\"", "");
            }
          
            return params;                
        }
        
        
}

